// Copyright Epic Games, Inc. All Rights Reserved.

#include <zencore/except.h>
#include <zencore/fmtutils.h>
#include <zencore/sharedbuffer.h>

#include <zencore/testing.h>

#include <memory.h>

#include <gsl/gsl-lite.hpp>

namespace zen {

//////////////////////////////////////////////////////////////////////////

UniqueBuffer
UniqueBuffer::Alloc(uint64_t Size)
{
	void* Buffer = Memory::Alloc(Size, 16);
	if (!Buffer)
	{
		ThrowOutOfMemory(fmt::format("failed allocating {:#x} bytes aligned to {:#x}", Size, 16));
	}
	IoBufferCore* Owner = new IoBufferCore(Buffer, Size);
	Owner->SetIsOwnedByThis(true);
	Owner->SetIsImmutable(false);

	return UniqueBuffer(Owner);
}

UniqueBuffer
UniqueBuffer::MakeMutableView(void* DataPtr, uint64_t Size)
{
	IoBufferCore* Owner = new IoBufferCore(DataPtr, Size);
	Owner->SetIsImmutable(false);
	return UniqueBuffer(Owner);
}

UniqueBuffer::UniqueBuffer(IoBufferCore* Owner) : m_Buffer(Owner)
{
}

SharedBuffer
UniqueBuffer::MoveToShared()
{
	return SharedBuffer(std::move(m_Buffer));
}

void
UniqueBuffer::Reset()
{
	m_Buffer = nullptr;
}

//////////////////////////////////////////////////////////////////////////

SharedBuffer::SharedBuffer(UniqueBuffer&& InBuffer) : m_Buffer(std::move(InBuffer.m_Buffer))
{
}

SharedBuffer
SharedBuffer::MakeOwned() const&
{
	if (IsOwned() || !m_Buffer)
	{
		return *this;
	}
	else
	{
		return Clone(GetView());
	}
}

SharedBuffer
SharedBuffer::MakeOwned() &&
{
	if (IsOwned())
	{
		return std::move(*this);
	}
	else
	{
		return Clone(GetView());
	}
}

SharedBuffer
SharedBuffer::MakeView(MemoryView View, SharedBuffer OuterBuffer)
{
	if (OuterBuffer)
	{
		ZEN_ASSERT(OuterBuffer.GetView().Contains(View));
	}

	if (View == OuterBuffer.GetView())
	{
		// Reference to the full buffer contents, so just return the "outer"
		return OuterBuffer;
	}

	IoBufferCore* NewCore = new IoBufferCore(OuterBuffer.m_Buffer, View.GetData(), View.GetSize());
	NewCore->SetIsImmutable(true);
	return SharedBuffer(NewCore);
}

SharedBuffer
SharedBuffer::MakeView(const void* Data, uint64_t Size)
{
	return SharedBuffer(new IoBufferCore(const_cast<void*>(Data), Size));
}

SharedBuffer
SharedBuffer::Clone()
{
	const uint64_t Size	  = GetSize();
	void*		   Buffer = Memory::Alloc(Size, 16);
	if (!Buffer)
	{
		ThrowOutOfMemory(fmt::format("failed allocating {:#x} bytes aligned to {:#x}", Size, 16));
	}
	auto NewOwner = new IoBufferCore(Buffer, Size);
	NewOwner->SetIsOwnedByThis(true);
	memcpy(Buffer, m_Buffer->DataPointer(), Size);

	return SharedBuffer(NewOwner);
}

SharedBuffer
SharedBuffer::Clone(MemoryView View)
{
	const uint64_t Size	  = View.GetSize();
	void*		   Buffer = Memory::Alloc(Size, 16);
	if (!Buffer)
	{
		ThrowOutOfMemory(fmt::format("failed allocating {:#x} bytes aligned to {:#x}", Size, 16));
	}
	auto NewOwner = new IoBufferCore(Buffer, Size);
	NewOwner->SetIsOwnedByThis(true);
	memcpy(Buffer, View.GetData(), Size);

	return SharedBuffer(NewOwner);
}

//////////////////////////////////////////////////////////////////////////

#if ZEN_WITH_TESTS

void
sharedbuffer_forcelink()
{
}

TEST_CASE("SharedBuffer")
{
}

#endif

}  // namespace zen
